/*
 * helpers.cpp
 *
 *  Created on: 6/11/2012
 *      Author: cvan680
 */


#include "helper.h"

namespace voreen {

GLubyte* Helper::renderArrowPlotTexture(tgt::vec2* flow, const tgt::vec2& textureSize, RenderPort& tempPort, const tgt::vec2& thresholds, int arrowSize, int arrowSpacing) {
//    prepareColorCoding2DShader(flow, thresholds);

    tempPort.activateTarget();

    beginOrthographicRendering(static_cast<tgt::ivec2>(textureSize));

    GLuint arrowList_ = 0; // TODO this one should be stored on MFTGSliceRenderer

    arrowList_ = buildArrowGridDisplayList(flow, textureSize, thresholds, arrowList_, arrowSize, arrowSpacing);

//    if ((shader_ != 0) && (shader_->isActivated() == true))
//        shader_->deactivate();

    endOrthographicRendering();

    return copyTargetToTexture(tempPort, static_cast<tgt::ivec2>(textureSize))->getPixelData();
}

tgt::Texture* Helper::copyTargetToTexture(RenderPort& renderPort, const tgt::vec2& textureSize) {
    renderPort.activateTarget();

    //glDrawBuffer(GL_COLOR_ATTACHMENT0_EXT);
    glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
    tgt::ivec2 size = static_cast<tgt::ivec2>(textureSize);
    tgt::Texture* texture = new tgt::Texture(tgt::vec3(textureSize, 1), GL_RGBA, GL_RGBA16,
        GL_FLOAT, tgt::Texture::NEAREST);
    glReadPixels(0, 0, size.x, size.y, GL_RGBA, GL_FLOAT, texture->getPixelData());

    renderPort.deactivateTarget();
    return texture;
}
void Helper::beginOrthographicRendering(const tgt::ivec2& viewport) {
    glPushAttrib(GL_ALL_ATTRIB_BITS);
    glViewport(0, 0, viewport.x, viewport.y);
    glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
    glClear(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT);
    glMatrixMode(GL_PROJECTION);
    glPushMatrix();
    glLoadIdentity();
    glOrtho(0.0, viewport.x, 0.0, viewport.y, -1.0, 1.0);
    glMatrixMode(GL_MODELVIEW);
    glPushMatrix();
    glLoadIdentity();
    glDisable(GL_DEPTH_TEST);
}
void Helper::endOrthographicRendering() {
    glEnable(GL_DEPTH_TEST);
    glPopAttrib();
    glMatrixMode(GL_PROJECTION);
    glPopMatrix();
    glMatrixMode(GL_MODELVIEW);
    glPopMatrix();
}

GLuint Helper::buildArrowGridDisplayList(tgt::vec2* flow, const tgt::vec2& textureSize, const tgt::vec2& thresholds, GLuint arrowList_, int as, int asp) {
    // size of an arrow (in pixels / zoomfactor)
	const int aSize = as; // TODO property
	const int aSpacing = asp; // TODO property

    const float arrowSize = static_cast<float>(aSize);

    // scaling factor for a single arrow relative to texture size
    const tgt::vec2 dim = static_cast<tgt::vec2>(textureSize);
    const float scaling = tgt::min(textureSize / dim);

    // spacing between the arrows in multiples of arrow length (in pixels)
    int spacing = aSpacing * aSize;
    if (spacing <= 0){ spacing = 1; }
    else if (spacing > tgt::min(textureSize)){ spacing = tgt::min(textureSize); }

    tgt::ivec2 ts = static_cast<tgt::ivec2>(textureSize);
    tgt::ivec2 grid = ts / spacing;

    if (arrowList_ != 0)
        glDeleteLists(arrowList_, 1);
    arrowList_ = glGenLists(1);

    glNewList(arrowList_, GL_COMPILE);
    int index;

    float vsize;

    for (int y = 0; y <= grid.y; ++y) {
        for (int x = 0; x <= grid.x; ++x) {
            tgt::ivec2 r(x * spacing, y * spacing);
            if(r.x < 0.0f || r.y < 0.0f || static_cast<int>(ceilf(r.x)) >= textureSize.x || static_cast<int>(ceilf(r.y)) >= textureSize.y ){ continue; }

            index = (int)(r.y*textureSize.x + r.x);
            tgt::vec2 v = flow[index];
            if (v == tgt::vec2::zero)
                continue;
/*
            if (thresholds != tgt::vec2::zero) {
                float magnitude = tgt::length(v);
                if ((magnitude < thresholds.x) || (magnitude > thresholds.y))
                    continue;
            }
*/
            vsize = vecSize(v);

            tgt::vec2 texCoord(r.x / dim.x, r.y / dim.y);
            glMultiTexCoord2fv(GL_TEXTURE0, texCoord.elem);

            if(vsize > 0.01){
            	tgt::mat3 trafo = getTransformationMatrix(r, v, (arrowSize * 1.5 *vsize));
            	renderArrow(trafo * scaling, vsize);
            }
        }   // for (x
    }   // for (y
    glEndList();

    if (arrowList_ != 0)
        glCallList(arrowList_);

    return arrowList_;
}
float Helper::vecSize(tgt::vec2 v){
	return sqrtf(tgt::dot(v, v));
}
tgt::vec4 Helper::getColor(float m){
	const int colorTableSize = 3;
	tgt::vec3 colorTable[colorTableSize];

	// TODO come up with good color coding
	/*
    colorTable[0] = tgt::vec3(0.0, 0.0, 0.0);
    colorTable[1] = tgt::vec3(0.5, 0.0, 0.0);
    colorTable[2] = tgt::vec3(1.0, 1.0, 1.0);
    */
    colorTable[0] = tgt::vec3(0.0, 0.0, 0.0);
    colorTable[1] = tgt::vec3(0.15, 0.35, 0.15);
    colorTable[2] = tgt::vec3(0.3, 0.7, 0.3);

    float numColors = float(colorTableSize - 1);
    float v = m*numColors;
    if(v < 0.0){ v = 0.0; }else if(v > numColors){ v = numColors; }
    tgt::ivec2 limits = tgt::ivec2(int(v), int(ceil(v))); //clamp( ,    0, colorTableSize );
    if(limits.x < 0.0){ limits.x = 0.0; }else if(limits.x > colorTableSize){ limits.x = colorTableSize; }
    if(limits.y < 0.0){ limits.y = 0.0; }else if(limits.y > colorTableSize){ limits.y = colorTableSize; }

    float fr = v - (int)(v);

    //tgt::vec3 color = colorTable[limits.x] *
    tgt::vec3 color = mix(colorTable[limits.x], colorTable[limits.y], fr);
    return tgt::vec4(color, 1.0);
}
void Helper::renderArrow(const tgt::mat3& transformation, float magnitude) {
    // basic geometry for the arrow (triangle + quad with tip pointing along
    // y-axis and bounding sizes of [0.5, 1.0], centered at [0.0, 0.0])
    tgt::vec3 tri[7] = {
        tgt::vec3(-0.25f, 0.0f, 1.0f),  // left on base of triangle
        tgt::vec3(0.25f, 0.0f, 1.0f),   // right on base of triangle
        tgt::vec3(0.0f, 0.5f, 1.0f),    // top of triangle

        tgt::vec3(-0.1f, -0.5f, 1.0f),  // lower left corner of quad
        tgt::vec3(0.1f, -0.5f, 1.0f),   // lower right corner of quad
        tgt::vec3(0.1f, 0.0f, 1.0f),    // upper right corner of quad
        tgt::vec3(-0.1f, 0.0f, 1.0f)    // upper left corner of quad
    };

    // apply transformation to every vertex
    for (size_t i = 0; i < 7; ++i){ tri[i] = transformation * tri[i]; }

    tgt::vec4 col = getColor(magnitude);
    glColor3f(col.r, col.g, col.b);
    glBegin(GL_TRIANGLES);
        glVertex2fv(tri[0]._xy);
        glVertex2fv(tri[1]._xy);
        glVertex2fv(tri[2]._xy);
    glEnd();
    glBegin(GL_QUADS);
        glVertex2fv(tri[3]._xy);
        glVertex2fv(tri[4]._xy);
        glVertex2fv(tri[5]._xy);
        glVertex2fv(tri[6]._xy);
    glEnd();
}
tgt::mat3 Helper::getTransformationMatrix(const tgt::vec2& r, const tgt::vec2& v, const float scaling) {
    tgt::vec2 tangent = FlowMath::normalize(v);
    if (tangent == tgt::vec2::zero)
        return tgt::mat3(scaling, 0.0f, 0.0f,
                         0.0f, scaling, 0.0f,
                         0.0f, 0.0f, 1.0f);

    tgt::vec2 normal(tangent.y, -tangent.x);

    if (scaling != 1.0f) {
        normal *= scaling;
        tangent *= scaling;
    }

    return tgt::mat3(normal.x, tangent.x, r.x,
        normal.y, tangent.y, r.y,
        0.0f, 0.0f, 1.0f);
}





/**********************
 * Render streamlines *
 **********************/
GLubyte* Helper::renderStreamlinesTexture(tgt::vec2* flow, const tgt::vec2& textureSize, int numberOfStreamlines) {
	int numPix = textureSize.x*textureSize.y;
	float* data = new float[numPix];
	memset(data, 0, sizeof(float) * numPix);

	// FIXME changed to start streamlines with seeds on regular grid

	// number of seeds in each dimension
	int nD = (int)sqrtf((float)numberOfStreamlines);
	int incX = (int) ((float)textureSize.x / (float)nD);
	int incY = (int) ((float)textureSize.y / (float)nD);
	tgt::vec2 start = tgt::vec2(0.5*incX, 0.5*incY);

	tgt::vec2 seed, fl;
	float size;
	int seedIndex;
	int counter = 800;
	/*
	for(int xi=0; xi<nD; xi++){
		for(int yi=0; yi<nD; yi++){
			seed = tgt::vec2(start.x + xi*incX, start.y + yi*incY);
			while(seed.x < textureSize.x && seed.x > 0 && seed.y < textureSize.y && seed.y > 0 && counter >= 0){
				seedIndex = static_cast<int>( seed.x ) + (static_cast<int>( seed.y )*textureSize.x);
				data[seedIndex] = 1.0;

				fl = flow[seedIndex];
				size = vecSize(fl);
				fl.x = fl.x / size;
				fl.y = fl.y / size;
				seed += fl; // follow streamline
				counter--; // decrease counter to make sure we stop at SOME point..
			}
			counter = 800;
		}
	}
	*/
	for(int i=0; i<numberOfStreamlines; i++){
		seed = tgt::vec2( (rand()/static_cast<float>(RAND_MAX))*textureSize.x, (rand()/static_cast<float>(RAND_MAX))*textureSize.y );
		while(seed.x < textureSize.x && seed.x > 0 && seed.y < textureSize.y && seed.y > 0 && counter >= 0){
			seedIndex = static_cast<int>( seed.x ) + (static_cast<int>( seed.y )*textureSize.x);
			data[seedIndex] = 1.0;

			fl = flow[seedIndex];
			size = vecSize(fl);
			fl.x = fl.x / size;
			fl.y = fl.y / size;
			seed += fl; // follow streamline
			counter--; // decrease counter to make sure we stop at SOME point..
		}
		counter = 800;
	}




	return reinterpret_cast<GLubyte*>(data);
}

}


